package com.zzw.java.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.zzw.java.mqtt.MqttGateway;
import com.zzw.java.mqtt.MqttInBoundConfiguration;
import com.zzw.java.pojo.HisDataVo;
import com.zzw.java.pojo.HistoricalDataVo;
import com.zzw.java.entity.LevelResult;
import com.zzw.java.service.DtuService;
import com.zzw.java.service.LevelResultService;
import com.zzw.java.service.SerialPortService;
import com.zzw.java.serial.ReceiveDataHandler;
import com.zzw.java.serial.ReceiveDataHandlerImpl;
import com.zzw.java.utils.ResponseResult;
import com.zzw.java.utils.SerialPortUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @Author：hcy
 * @Package：com.zzw.java.serial.spring
 * @version：1.0
 * @Date：2025/8/6 14:01
 */

@Service
@Slf4j
public class SerialPortServiceImp implements SerialPortService {

    @Autowired
    private ReceiveDataHandler receiveDataHandler;

    @Resource
    private LevelResultService levelResultService;

    @Resource
    private MqttGateway mqttGateWay;

    @Resource
    private DtuService dtuService;

    public List<SerialPort> serialPortList;

    // 设备码
    public static String deviceCode = "";

    // 测量间隔时间(单位：毫秒)
    public static long measureInterval = 1000;

    // 连接的通道名
//     public static String connectSystemPortName = "COM3";
    public static String connectSystemPortName = "ttymxc1";

    public static String CRLF = "\r\n";

    /**
     * mis响应数据topic
     */
    public static String sendHisTopic = "/hisData";




    @PostConstruct
    public void initSerialPort() {
         log.info("mis串口程序启动，查询串口列表............");
        // 查询所有串口
        discoverSerialPort();

        new Timer().schedule(new TimerTask() {

            @Override
            public void run() {
                log.info("开始查询历史数据............");
                String hisDataTopic = MqttInBoundConfiguration.sendTopic.concat(sendHisTopic);
                //启动时先查看未上传的历史数据
                LambdaQueryWrapper<LevelResult> lambdaQueryWrapper = new LambdaQueryWrapper<LevelResult>().eq(LevelResult::getUploadStatus, 0);
                List<LevelResult> list = levelResultService.list(lambdaQueryWrapper);
                if (CollUtil.isNotEmpty(list)){
                    ArrayList<HisDataVo> hisDataVos = new ArrayList<>();
                    Map<Long, List<LevelResult>> listMap = list.stream().collect(Collectors.groupingBy(LevelResult::getTaskId));
                    for (Long aLong : listMap.keySet()) {
                        List<LevelResult> resultList =  listMap.get(aLong).stream()
                                .sorted(Comparator.comparingInt(lr -> Integer.parseInt(lr.getSensorNo())))
                                .collect(Collectors.toList());
                        StringBuilder sb = new StringBuilder();
                        for (LevelResult levelResult : resultList) {
                            if (StrUtil.isNotBlank(levelResult.getSurveyData())) {
                                sb.append(levelResult.getSurveyData().replace(":"," ")).append(",");
                            }else {
                                continue;
                            }
                        }
                        if (sb.length()>0&&sb.charAt(sb.length() - 1)==',') sb.deleteCharAt(sb.length()-1);
                        HisDataVo build = HisDataVo.builder().levelRawDatas(sb.toString()).surveyTime(aLong.toString()).build();
                        hisDataVos.add(build);
                        sb.setLength(0);
                    }
                    HistoricalDataVo historicalDataVo = new HistoricalDataVo();
                    if (CollUtil.isNotEmpty(hisDataVos)) {
                        historicalDataVo.setHisData(hisDataVos);
                        historicalDataVo.setTransferModuleMac(ReceiveDataHandlerImpl.TRANSFER_MODULE_MAC);
                        mqttGateWay.sendToMqtt(hisDataTopic,1,JSONUtil.toJsonStr(historicalDataVo));
                        log.info("MQTT历史数据发送成功；TOPIC:{},数据:{}",hisDataTopic,JSONUtil.toJsonStr(historicalDataVo));
                    }
                }
            }
        }, 60 * 1000);

        /*
            延迟1分钟连接串口
            新工控机只有4个串口(ttymxc1,ttymxc2,ttymxc3,ttymxc4),固定使用ttymxc1作为连接传感器的串口
            程序启动60秒后连接串口
        */
        new Timer().schedule(new TimerTask() {

            @Override
            public void run() {
                openSerialPort(connectSystemPortName);
            }
        }, 2*60 * 1000);

        // 定时发送初始化数据给仪器,发送指令给仪器(:FF01FF)
        new Timer().schedule(new TimerTask() {

            @Override
            public void run() {
                if (!StringUtils.hasText(deviceCode)) {
                    String data = ":FF01FF";
                    sendData(connectSystemPortName, data);
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    // 测试时看到是发送了指令的,但是没有收到仪器回应的指令,需要发两次才能收到(暂不清楚原因)
                    sendData(connectSystemPortName, data);
                }
            }
        }, 3 * 60 * 1000);

        // 仪器开机24小时后定时重启
        // new Timer().schedule(new TimerTask() {
        //
        //     @Override
        //     public void run() {
        //         try {
        //             // 执行命令
        //             Process process = Runtime.getRuntime().exec("/sbin/reboot");
        //
        //             process.getOutputStream().close();
        //         } catch (Exception e) {
        //             log.error("重启异常", e);
        //         }
        //     }
        // }, 24 * 60 * 60 * 1000);
    }


    @Override
    public List<SerialPort> discoverSerialPort() {
        serialPortList = Arrays.asList(SerialPort.getCommPorts());
        // 遍历串口列表并打印每个串口的名称
        for (SerialPort port : serialPortList) {
            log.info("找到串口: " + port.getSystemPortName());
        }
        return serialPortList;
    }


    // 关闭串口
    @Override
    public boolean closeSerialPort(String systemPortName) {
        for (SerialPort port : serialPortList) {
            if (port.getSystemPortName().equals(systemPortName)) {
                log.info("关闭串口:" + port.getSystemPortName());
                // 关闭串口
                return port.closePort();
            }
        }
        return false;
    }


    // 发送数据到串口
    @Override
    public void sendData(String systemPortName, String data) {
        // 发送数据注意，提前与接收设备沟通好协议，发送什么样类型的数据设备才可以进行响应，否则设备无响应
        for (SerialPort port : serialPortList) {
            if (port.getSystemPortName().equals(systemPortName)) {
                if (!port.isOpen()) {
                    // 没有连接串口需要先连接
                    if (!port.openPort()) {
                        log.error("串口未连通,发送数据失败。");
                        return;
                    }
                }
                StringBuilder dataBuilder = new StringBuilder(data);
                dataBuilder.append(CRLF);
                byte[] buffer2 = dataBuilder.toString().getBytes();
                port.writeBytes(buffer2, buffer2.length);
            }
        }
    }


    // 打开串口,接收数据
    @Override
    public ResponseResult<Boolean> openSerialPort(String systemPortName) {
        for (SerialPort port : serialPortList) {
            if (port.getSystemPortName().equals(systemPortName)) {
                int baudRate = 9600; // 波特率
                int dataBits = 8; // 数据位
                int stopBits = SerialPort.ONE_STOP_BIT; // 停止位
                int parity = SerialPort.NO_PARITY; // 校验位
                if (port.openPort()) {
                    try {
                        boolean setComResult = port.setComPortParameters(baudRate, dataBits, stopBits, parity); // 设置参数
                        boolean setComTimes = port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 20000, 0); // 设置超时
                        if (setComResult && setComTimes) {
                            connectSystemPortName = systemPortName;
                            // 创建数据监听器
                            port.addDataListener(new SerialPortDataListener() {
                                @Override
                                public int getListeningEvents() {
                                    return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
                                }

                                // 防止串口消息一次没有收到完整数据,这里使用StringBuilder进行数据拼接
                                private StringBuilder dataBuffer = new StringBuilder();


                                @Override
                                public void serialEvent(SerialPortEvent event) {
                                    if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
                                        // 接收数据
                                        byte[] readBuffer = new byte[port.bytesAvailable()]; // 调整数组大小以适应预期的响应长度
                                        int numRead = port.readBytes(readBuffer, readBuffer.length);
                                        if (numRead > 0) {
                                            String data = new String(readBuffer);
                                            log.info("receive data:" + data);
                                            // 校验数据是否合法(排除一些串口收到的乱码数据)
                                            if (checkDataValid(data) || (data.length() > 25 && data.length() < 30)) {
                                                // 接收的测量数据中有错误数据
                                                // 例：:012120+0.00000 +99838022(数据中间有空格) 这种放到后面处理
                                                dataBuffer.append(data);
                                            }

                                            if (!dataBuffer.toString().endsWith(CRLF)) {
                                                // 数据未接收完整
                                                return;
                                            }

                                            // 在这里进行收到数据的处理操作，例如加入阻塞队列，另一个模块消费队列解析收到的数据
                                            try {
                                                int dataSize = dataBuffer.length();
                                                if (dataSize >= 25 && dataSize < 30) {
                                                    // 这个长度收到的是测量数据
                                                    receiveDataHandler.handlerSensorData(port, dataBuffer.toString());
                                                } else if (dataSize >= 7 && dataSize < 10) {
                                                    // 这个长度收到的是设备码
                                                    String code = dataBuffer.substring(1, 3);
                                                    if (code.matches("\\d+")) {  // 正则判断是否全为数字
                                                        deviceCode = code;
                                                    } else {
                                                        log.error("设备码不是数字类型: {}", code);
                                                    }
                                                }
                                            } catch (Exception e) {
                                                log.error("接收数据处理异常", e);
                                            } finally {
                                                dataBuffer.setLength(0);
                                            }

                                        }
                                    }
                                }
                            });
                        } else {
                            log.error("串口{}参数有误", systemPortName);
                            return ResponseResult.getErrorResult("打开串口失败：" + port.getSystemPortName());
                        }
                    } catch (Exception e) {
                        log.error("打开串口{}异常", systemPortName, e);
                        return ResponseResult.getErrorResult("打开串口异常：" + port.getSystemPortName() + e.getMessage());
                    }
                    log.info("串口{}连接成功", systemPortName);
                    return ResponseResult.getSuccessResult(systemPortName + "串口连接成功");
                } else {
                    log.error("串口{}打开失败", systemPortName);
                    return ResponseResult.getErrorResult("串口打开失败：" + port.getSystemPortName());
                }
            }
        }
        return ResponseResult.getErrorResult("没有找到串口：" + systemPortName);
    }


    /**
     * 定时任务,发送第一个测量指令给仪器(每20分钟发送一次)
     */
    @Scheduled(cron = "0 0/20 * * * ?")
//    @Scheduled(cron = "0 * * * * ?")
    public void task() {
        if (StringUtils.hasText(deviceCode)) {
            // 例(:012101FF)
            String command = ":" + deviceCode + "2101FF" + CRLF;
            for (SerialPort port : serialPortList) {
                if (port.getSystemPortName().equals(connectSystemPortName)) {
                    // 发送第一个测量命令,开始后面的测量流程
                    SerialPortUtils.sendData(port, command);
                }
            }
            // 重新初始化通道序号
            ReceiveDataHandlerImpl.passage = 2;
        }
    }

    /**
     * 定时任务,定时重启
     */
    @Scheduled(cron = "0 0 3 * * ?")
    public void reboot() {
        try {
            //更新软件包；睡眠10s,等待dtu升级完成
//            dtuService.upgrade();
//            Thread.sleep(10000);
            // 执行命令
            Process process = Runtime.getRuntime().exec("/sbin/reboot");

            process.getOutputStream().close();
        } catch (Exception e) {
            log.error("重启异常", e);
        }
    }


    /**
     * 监测数据是否合法
     *
     * @param data 数据
     * @return 返回值
     */
    public static boolean checkDataValid(String data) {
        boolean isValid = true;
        for (int i = 0; i < data.length(); i++) {
            char c = data.charAt(i);
            if (!((c >= 'a' && c <= 'z') ||
                    (c >= 'A' && c <= 'Z') ||
                    (c >= '0' && c <= '9') ||
                    c == '+' || c == '-' || c == '.' || c == ':' ||
                    c == '\n' || c == '\r'
            )) {
                isValid = false;
                break;
            }
        }
        return isValid;
    }

}

